/*
 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.plaf.synth;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Enumeration;
import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import sun.swing.plaf.synth.SynthIcon;

/**
 * Provides the Synth L&amp;F UI delegate for
 * {@link javax.swing.JTree}.
 *
 * @author Scott Violet
 * @since 1.7
 */
public class SynthTreeUI extends BasicTreeUI
    implements PropertyChangeListener, SynthUI {

  private SynthStyle style;
  private SynthStyle cellStyle;

  private SynthContext paintContext;

  private boolean drawHorizontalLines;
  private boolean drawVerticalLines;

  private Object linesStyle;

  private int padding;

  private boolean useTreeColors;

  private Icon expandedIconWrapper = new ExpandedIconWrapper();

  /**
   * Creates a new UI object for the given component.
   *
   * @param x component to create UI object for
   * @return the UI object
   */
  public static ComponentUI createUI(JComponent x) {
    return new SynthTreeUI();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Icon getExpandedIcon() {
    return expandedIconWrapper;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void installDefaults() {
    updateStyle(tree);
  }

  private void updateStyle(JTree tree) {
    SynthContext context = getContext(tree, ENABLED);
    SynthStyle oldStyle = style;

    style = SynthLookAndFeel.updateStyle(context, this);
    if (style != oldStyle) {
      Object value;

      setExpandedIcon(style.getIcon(context, "Tree.expandedIcon"));
      setCollapsedIcon(style.getIcon(context, "Tree.collapsedIcon"));

      setLeftChildIndent(style.getInt(context, "Tree.leftChildIndent",
          0));
      setRightChildIndent(style.getInt(context, "Tree.rightChildIndent",
          0));

      drawHorizontalLines = style.getBoolean(
          context, "Tree.drawHorizontalLines", true);
      drawVerticalLines = style.getBoolean(
          context, "Tree.drawVerticalLines", true);
      linesStyle = style.get(context, "Tree.linesStyle");

      value = style.get(context, "Tree.rowHeight");
      if (value != null) {
        LookAndFeel.installProperty(tree, "rowHeight", value);
      }

      value = style.get(context, "Tree.scrollsOnExpand");
      LookAndFeel.installProperty(tree, "scrollsOnExpand",
          value != null ? value : Boolean.TRUE);

      padding = style.getInt(context, "Tree.padding", 0);

      largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0);

      useTreeColors = style.getBoolean(context,
          "Tree.rendererUseTreeColors", true);

      Boolean showsRootHandles = style.getBoolean(
          context, "Tree.showsRootHandles", Boolean.TRUE);
      LookAndFeel.installProperty(
          tree, JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles);

      if (oldStyle != null) {
        uninstallKeyboardActions();
        installKeyboardActions();
      }
    }
    context.dispose();

    context = getContext(tree, Region.TREE_CELL, ENABLED);
    cellStyle = SynthLookAndFeel.updateStyle(context, this);
    context.dispose();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void installListeners() {
    super.installListeners();
    tree.addPropertyChangeListener(this);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public SynthContext getContext(JComponent c) {
    return getContext(c, SynthLookAndFeel.getComponentState(c));
  }

  private SynthContext getContext(JComponent c, int state) {
    return SynthContext.getContext(c, style, state);
  }

  private SynthContext getContext(JComponent c, Region region) {
    return getContext(c, region, getComponentState(c, region));
  }

  private SynthContext getContext(JComponent c, Region region, int state) {
    return SynthContext.getContext(c, region, cellStyle, state);
  }

  private int getComponentState(JComponent c, Region region) {
    // Always treat the cell as selected, will be adjusted appropriately
    // when painted.
    return ENABLED | SELECTED;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected TreeCellEditor createDefaultCellEditor() {
    TreeCellRenderer renderer = tree.getCellRenderer();
    DefaultTreeCellEditor editor;

    if (renderer != null && (renderer instanceof DefaultTreeCellRenderer)) {
      editor = new SynthTreeCellEditor(tree, (DefaultTreeCellRenderer)
          renderer);
    } else {
      editor = new SynthTreeCellEditor(tree, null);
    }
    return editor;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected TreeCellRenderer createDefaultCellRenderer() {
    return new SynthTreeCellRenderer();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void uninstallDefaults() {
    SynthContext context = getContext(tree, ENABLED);

    style.uninstallDefaults(context);
    context.dispose();
    style = null;

    context = getContext(tree, Region.TREE_CELL, ENABLED);
    cellStyle.uninstallDefaults(context);
    context.dispose();
    cellStyle = null;

    if (tree.getTransferHandler() instanceof UIResource) {
      tree.setTransferHandler(null);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void uninstallListeners() {
    super.uninstallListeners();
    tree.removePropertyChangeListener(this);
  }

  /**
   * Notifies this UI delegate to repaint the specified component.
   * This method paints the component background, then calls
   * the {@link #paint(SynthContext, Graphics)} method.
   *
   * <p>In general, this method does not need to be overridden by subclasses.
   * All Look and Feel rendering code should reside in the {@code paint} method.
   *
   * @param g the {@code Graphics} object used for painting
   * @param c the component being painted
   * @see #paint(SynthContext, Graphics)
   */
  @Override
  public void update(Graphics g, JComponent c) {
    SynthContext context = getContext(c);

    SynthLookAndFeel.update(context, g);
    context.getPainter().paintTreeBackground(context,
        g, 0, 0, c.getWidth(), c.getHeight());
    paint(context, g);
    context.dispose();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void paintBorder(SynthContext context, Graphics g, int x,
      int y, int w, int h) {
    context.getPainter().paintTreeBorder(context, g, x, y, w, h);
  }

  /**
   * Paints the specified component according to the Look and Feel.
   * <p>This method is not used by Synth Look and Feel.
   * Painting is handled by the {@link #paint(SynthContext, Graphics)} method.
   *
   * @param g the {@code Graphics} object used for painting
   * @param c the component being painted
   * @see #paint(SynthContext, Graphics)
   */
  @Override
  public void paint(Graphics g, JComponent c) {
    SynthContext context = getContext(c);

    paint(context, g);
    context.dispose();
  }

  /**
   * Paints the specified component.
   *
   * @param context context for the component being painted
   * @param g the {@code Graphics} object used for painting
   * @see #update(Graphics, JComponent)
   */
  protected void paint(SynthContext context, Graphics g) {
    paintContext = context;

    updateLeadSelectionRow();

    Rectangle paintBounds = g.getClipBounds();
    Insets insets = tree.getInsets();
    TreePath initialPath = getClosestPathForLocation(tree, 0,
        paintBounds.y);
    Enumeration paintingEnumerator = treeState.getVisiblePathsFrom
        (initialPath);
    int row = treeState.getRowForPath(initialPath);
    int endY = paintBounds.y + paintBounds.height;
    TreeModel treeModel = tree.getModel();
    SynthContext cellContext = getContext(tree, Region.TREE_CELL);

    drawingCache.clear();

    setHashColor(context.getStyle().getColor(context,
        ColorType.FOREGROUND));

    if (paintingEnumerator != null) {
      // First pass, draw the rows

      boolean done = false;
      boolean isExpanded;
      boolean hasBeenExpanded;
      boolean isLeaf;
      Rectangle rowBounds = new Rectangle(0, 0, tree.getWidth(), 0);
      Rectangle bounds;
      TreePath path;
      TreeCellRenderer renderer = tree.getCellRenderer();
      DefaultTreeCellRenderer dtcr = (renderer instanceof
          DefaultTreeCellRenderer) ? (DefaultTreeCellRenderer)
          renderer : null;

      configureRenderer(cellContext);
      while (!done && paintingEnumerator.hasMoreElements()) {
        path = (TreePath) paintingEnumerator.nextElement();
        bounds = getPathBounds(tree, path);
        if ((path != null) && (bounds != null)) {
          isLeaf = treeModel.isLeaf(path.getLastPathComponent());
          if (isLeaf) {
            isExpanded = hasBeenExpanded = false;
          } else {
            isExpanded = treeState.getExpandedState(path);
            hasBeenExpanded = tree.hasBeenExpanded(path);
          }
          rowBounds.y = bounds.y;
          rowBounds.height = bounds.height;
          paintRow(renderer, dtcr, context, cellContext, g,
              paintBounds, insets, bounds, rowBounds, path,
              row, isExpanded, hasBeenExpanded, isLeaf);
          if ((bounds.y + bounds.height) >= endY) {
            done = true;
          }
        } else {
          done = true;
        }
        row++;
      }

      // Draw the connecting lines and controls.
      // Find each parent and have them draw a line to their last child
      boolean rootVisible = tree.isRootVisible();
      TreePath parentPath = initialPath;
      parentPath = parentPath.getParentPath();
      while (parentPath != null) {
        paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
        drawingCache.put(parentPath, Boolean.TRUE);
        parentPath = parentPath.getParentPath();
      }
      done = false;
      paintingEnumerator = treeState.getVisiblePathsFrom(initialPath);
      while (!done && paintingEnumerator.hasMoreElements()) {
        path = (TreePath) paintingEnumerator.nextElement();
        bounds = getPathBounds(tree, path);
        if ((path != null) && (bounds != null)) {
          isLeaf = treeModel.isLeaf(path.getLastPathComponent());
          if (isLeaf) {
            isExpanded = hasBeenExpanded = false;
          } else {
            isExpanded = treeState.getExpandedState(path);
            hasBeenExpanded = tree.hasBeenExpanded(path);
          }
          // See if the vertical line to the parent has been drawn.
          parentPath = path.getParentPath();
          if (parentPath != null) {
            if (drawingCache.get(parentPath) == null) {
              paintVerticalPartOfLeg(g, paintBounds, insets,
                  parentPath);
              drawingCache.put(parentPath, Boolean.TRUE);
            }
            paintHorizontalPartOfLeg(g,
                paintBounds, insets, bounds,
                path, row, isExpanded,
                hasBeenExpanded, isLeaf);
          } else if (rootVisible && row == 0) {
            paintHorizontalPartOfLeg(g,
                paintBounds, insets, bounds,
                path, row, isExpanded,
                hasBeenExpanded, isLeaf);
          }
          if (shouldPaintExpandControl(path, row, isExpanded,
              hasBeenExpanded, isLeaf)) {
            paintExpandControl(g, paintBounds,
                insets, bounds, path, row,
                isExpanded, hasBeenExpanded, isLeaf);
          }
          if ((bounds.y + bounds.height) >= endY) {
            done = true;
          }
        } else {
          done = true;
        }
        row++;
      }
    }
    cellContext.dispose();

    paintDropLine(g);

    // Empty out the renderer pane, allowing renderers to be gc'ed.
    rendererPane.removeAll();

    paintContext = null;
  }

  private void configureRenderer(SynthContext context) {
    TreeCellRenderer renderer = tree.getCellRenderer();

    if (renderer instanceof DefaultTreeCellRenderer) {
      DefaultTreeCellRenderer r = (DefaultTreeCellRenderer) renderer;
      SynthStyle style = context.getStyle();

      context.setComponentState(ENABLED | SELECTED);
      Color color = r.getTextSelectionColor();
      if (color == null || (color instanceof UIResource)) {
        r.setTextSelectionColor(style.getColor(
            context, ColorType.TEXT_FOREGROUND));
      }
      color = r.getBackgroundSelectionColor();
      if (color == null || (color instanceof UIResource)) {
        r.setBackgroundSelectionColor(style.getColor(
            context, ColorType.TEXT_BACKGROUND));
      }

      context.setComponentState(ENABLED);
      color = r.getTextNonSelectionColor();
      if (color == null || color instanceof UIResource) {
        r.setTextNonSelectionColor(style.getColorForState(
            context, ColorType.TEXT_FOREGROUND));
      }
      color = r.getBackgroundNonSelectionColor();
      if (color == null || color instanceof UIResource) {
        r.setBackgroundNonSelectionColor(style.getColorForState(
            context, ColorType.TEXT_BACKGROUND));
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
      Insets insets, Rectangle bounds,
      TreePath path, int row,
      boolean isExpanded,
      boolean hasBeenExpanded, boolean
      isLeaf) {
    if (drawHorizontalLines) {
      super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds,
          path, row, isExpanded,
          hasBeenExpanded, isLeaf);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void paintHorizontalLine(Graphics g, JComponent c, int y,
      int left, int right) {
    paintContext.getStyle().getGraphicsUtils(paintContext).drawLine(
        paintContext, "Tree.horizontalLine", g, left, y, right, y, linesStyle);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void paintVerticalPartOfLeg(Graphics g,
      Rectangle clipBounds, Insets insets,
      TreePath path) {
    if (drawVerticalLines) {
      super.paintVerticalPartOfLeg(g, clipBounds, insets, path);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
      int bottom) {
    paintContext.getStyle().getGraphicsUtils(paintContext).drawLine(
        paintContext, "Tree.verticalLine", g, x, top, x, bottom, linesStyle);
  }

  private void paintRow(TreeCellRenderer renderer,
      DefaultTreeCellRenderer dtcr, SynthContext treeContext,
      SynthContext cellContext, Graphics g, Rectangle clipBounds,
      Insets insets, Rectangle bounds, Rectangle rowBounds,
      TreePath path, int row, boolean isExpanded,
      boolean hasBeenExpanded, boolean isLeaf) {
    // Don't paint the renderer if editing this row.
    boolean selected = tree.isRowSelected(row);

    JTree.DropLocation dropLocation = tree.getDropLocation();
    boolean isDrop = dropLocation != null
        && dropLocation.getChildIndex() == -1
        && path == dropLocation.getPath();

    int state = ENABLED;
    if (selected || isDrop) {
      state |= SELECTED;
    }

    if (tree.isFocusOwner() && row == getLeadSelectionRow()) {
      state |= FOCUSED;
    }

    cellContext.setComponentState(state);

    if (dtcr != null && (dtcr.getBorderSelectionColor() instanceof
        UIResource)) {
      dtcr.setBorderSelectionColor(style.getColor(
          cellContext, ColorType.FOCUS));
    }
    SynthLookAndFeel.updateSubregion(cellContext, g, rowBounds);
    cellContext.getPainter().paintTreeCellBackground(cellContext, g,
        rowBounds.x, rowBounds.y, rowBounds.width,
        rowBounds.height);
    cellContext.getPainter().paintTreeCellBorder(cellContext, g,
        rowBounds.x, rowBounds.y, rowBounds.width,
        rowBounds.height);
    if (editingComponent != null && editingRow == row) {
      return;
    }

    int leadIndex;

    if (tree.hasFocus()) {
      leadIndex = getLeadSelectionRow();
    } else {
      leadIndex = -1;
    }

    Component component = renderer.getTreeCellRendererComponent(
        tree, path.getLastPathComponent(),
        selected, isExpanded, isLeaf, row,
        (leadIndex == row));

    rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
        bounds.width, bounds.height, true);
  }

  private int findCenteredX(int x, int iconWidth) {
    return tree.getComponentOrientation().isLeftToRight()
        ? x - (int) Math.ceil(iconWidth / 2.0)
        : x - (int) Math.floor(iconWidth / 2.0);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void paintExpandControl(Graphics g, Rectangle clipBounds,
      Insets insets, Rectangle bounds, TreePath path, int row,
      boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
    //modify the paintContext's state to match the state for the row
    //this is a hack in that it requires knowledge of the subsequent
    //method calls. The point is, the context used in drawCentered
    //should reflect the state of the row, not of the tree.
    boolean isSelected = tree.getSelectionModel().isPathSelected(path);
    int state = paintContext.getComponentState();
    if (isSelected) {
      paintContext.setComponentState(state | SynthConstants.SELECTED);
    }
    super.paintExpandControl(g, clipBounds, insets, bounds, path, row,
        isExpanded, hasBeenExpanded, isLeaf);
    paintContext.setComponentState(state);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void drawCentered(Component c, Graphics graphics, Icon icon,
      int x, int y) {
    int w = SynthIcon.getIconWidth(icon, paintContext);
    int h = SynthIcon.getIconHeight(icon, paintContext);

    SynthIcon.paintIcon(icon, paintContext, graphics,
        findCenteredX(x, w),
        y - h / 2, w, h);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void propertyChange(PropertyChangeEvent event) {
    if (SynthLookAndFeel.shouldUpdateStyle(event)) {
      updateStyle((JTree) event.getSource());
    }

    if ("dropLocation" == event.getPropertyName()) {
      JTree.DropLocation oldValue = (JTree.DropLocation) event.getOldValue();
      repaintDropLocation(oldValue);
      repaintDropLocation(tree.getDropLocation());
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void paintDropLine(Graphics g) {
    JTree.DropLocation loc = tree.getDropLocation();
    if (!isDropLine(loc)) {
      return;
    }

    Color c = (Color) style.get(paintContext, "Tree.dropLineColor");
    if (c != null) {
      g.setColor(c);
      Rectangle rect = getDropLineRect(loc);
      g.fillRect(rect.x, rect.y, rect.width, rect.height);
    }
  }

  private void repaintDropLocation(JTree.DropLocation loc) {
    if (loc == null) {
      return;
    }

    Rectangle r;

    if (isDropLine(loc)) {
      r = getDropLineRect(loc);
    } else {
      r = tree.getPathBounds(loc.getPath());
      if (r != null) {
        r.x = 0;
        r.width = tree.getWidth();
      }
    }

    if (r != null) {
      tree.repaint(r);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int getRowX(int row, int depth) {
    return super.getRowX(row, depth) + padding;
  }


  private class SynthTreeCellRenderer extends DefaultTreeCellRenderer
      implements UIResource {

    SynthTreeCellRenderer() {
    }

    @Override
    public String getName() {
      return "Tree.cellRenderer";
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
        boolean sel,
        boolean expanded,
        boolean leaf, int row,
        boolean hasFocus) {
      if (!useTreeColors && (sel || hasFocus)) {
        SynthLookAndFeel.setSelectedUI((SynthLabelUI) SynthLookAndFeel.
                getUIOfType(getUI(), SynthLabelUI.class),
            sel, hasFocus, tree.isEnabled(), false);
      } else {
        SynthLookAndFeel.resetSelectedUI();
      }
      return super.getTreeCellRendererComponent(tree, value, sel,
          expanded, leaf, row, hasFocus);
    }

    @Override
    public void paint(Graphics g) {
      paintComponent(g);
      if (hasFocus) {
        SynthContext context = getContext(tree, Region.TREE_CELL);

        if (context.getStyle() == null) {
          assert false : "SynthTreeCellRenderer is being used " +
              "outside of UI that created it";
          return;
        }
        int imageOffset = 0;
        Icon currentI = getIcon();

        if (currentI != null && getText() != null) {
          imageOffset = currentI.getIconWidth() +
              Math.max(0, getIconTextGap() - 1);
        }
        if (selected) {
          context.setComponentState(ENABLED | SELECTED);
        } else {
          context.setComponentState(ENABLED);
        }
        if (getComponentOrientation().isLeftToRight()) {
          context.getPainter().paintTreeCellFocus(context, g,
              imageOffset, 0, getWidth() - imageOffset,
              getHeight());
        } else {
          context.getPainter().paintTreeCellFocus(context, g,
              0, 0, getWidth() - imageOffset, getHeight());
        }
        context.dispose();
      }
      SynthLookAndFeel.resetSelectedUI();
    }
  }


  private static class SynthTreeCellEditor extends DefaultTreeCellEditor {

    public SynthTreeCellEditor(JTree tree,
        DefaultTreeCellRenderer renderer) {
      super(tree, renderer);
      setBorderSelectionColor(null);
    }

    @Override
    protected TreeCellEditor createTreeCellEditor() {
      JTextField tf = new JTextField() {
        @Override
        public String getName() {
          return "Tree.cellEditor";
        }
      };
      DefaultCellEditor editor = new DefaultCellEditor(tf);

      // One click to edit.
      editor.setClickCountToStart(1);
      return editor;
    }
  }

  //
  // BasicTreeUI directly uses expandIcon outside of the Synth methods.
  // To get the correct context we return an instance of this that fetches
  // the SynthContext as needed.
  //
  private class ExpandedIconWrapper extends SynthIcon {

    public void paintIcon(SynthContext context, Graphics g, int x,
        int y, int w, int h) {
      if (context == null) {
        context = getContext(tree);
        SynthIcon.paintIcon(expandedIcon, context, g, x, y, w, h);
        context.dispose();
      } else {
        SynthIcon.paintIcon(expandedIcon, context, g, x, y, w, h);
      }
    }

    public int getIconWidth(SynthContext context) {
      int width;
      if (context == null) {
        context = getContext(tree);
        width = SynthIcon.getIconWidth(expandedIcon, context);
        context.dispose();
      } else {
        width = SynthIcon.getIconWidth(expandedIcon, context);
      }
      return width;
    }

    public int getIconHeight(SynthContext context) {
      int height;
      if (context == null) {
        context = getContext(tree);
        height = SynthIcon.getIconHeight(expandedIcon, context);
        context.dispose();
      } else {
        height = SynthIcon.getIconHeight(expandedIcon, context);
      }
      return height;
    }
  }
}
